'''
Languages supported by the GNU Compiler Collection.
'''

from __future__ import with_statement

import re
import subprocess

from os_helpers import LocalCwd
import py_helpers
from . import _base, _lang

NAME = "GNU Compiler Collection"

# Version numbers to check for.
MIN_MAJOR_VERSION = 3
MAX_MAJOR_VERSION = 4
MAX_MINOR_VERSION = 9

# Check for alternate versions of GCC suite programs:
# by append nothing (not even ".exe", so APPEND_NONE is idempotent)
APPEND_NONE = 0
# by appending major version number (and ".exe" if needed)
APPEND_MAJOR = 1
# by appending version number (and ".exe" if needed)
APPEND_MINOR = 2

# Type of GNU environment:
GNU_STANDARD = 0
GNU_WINDOWS = 1
GNU_CYGWIN = 2

def expand_path_list(host, base_path, append):
    """
    Returns a list of base_path possibilities for the given append option.
    
    For example:
    expand_path_list(nt_host, 'c:\cygwin\bin\gcc.exe', APPEND_NONE)
        returns [ r'c:\cygwin\bin\gcc.exe' ]
    expand_path_list(nt_host, 'gcc', APPEND_MAJOR)
        returns [ 'gcc-3.exe', 'gcc-4.exe' ]
    """
    if append == APPEND_NONE:
        return [ base_path ]
    
    if append == APPEND_MAJOR:
        result = [ "%s-%s" % (base_path, version) 
                  for version in range(MIN_MAJOR_VERSION, MAX_MAJOR_VERSION + 1)] 
    else:
        result = [ "%s-%s.%s" % (base_path, version[0], version[1])
                  for version in py_helpers.product(
                        range(MIN_MAJOR_VERSION, MAX_MAJOR_VERSION + 1),
                        range(0, MAX_MINOR_VERSION + 1)) ]
    
    if 'nt' in host.os_name_list:
        result = [ x + '.exe' for x in result ]
    
    return result

def check_using_exec(host, base_path, append, 
                     language, base_description, description, gnu_type, 
                     environment_list):
    """
    Checks a single executable name to see if it's runnable as a GCC suite 
    executable. 
    """
    for exe in expand_path_list(host, base_path, append):
        gcc = host.popen([exe, '--version'])
        stdoutdata, stderrdata = gcc.communicate()
        all_data = stdoutdata + stderrdata
        
        m = re.match(r'%s(?:\.exe)? .*([0-9]+\.[0-9]+\.[0-9]+)' 
                     % re.escape(host.path_basename(exe).replace(".exe", "")), all_data)
        if m:
            version = m.group(1)
            # The base name for the GCC program is probably a symlink to a
            # particular version, so we need to check for duplicates here.
            if not [ x.version == version for x in environment_list[language.name][description] ]:
                environment_list[language.name][description].append(GnuEnvironment(language, host, exe, version, gnu_type))

def check_using_glob(host, base_path, append, 
                     language, base_description, description, gnu_type,
                     environment_list):
    """
    Checks a path glob (wildcard) for GCC suite executables, and adds any found
    to environment_list.
    """
    for exe in expand_path_list(host, base_path, append):
        if host.path_exists(exe):
            check_using_exec(host, exe, APPEND_NONE,
                             language, base_description, description, gnu_type,
                             environment_list)

def check_using_which(host, base_path, append,
                      language, base_description, description, gnu_type,
                      environment_list):
    for exe in expand_path_list(host, base_path, append):
        which = host.popen(['which', exe])
        which.wait()
        if which.returncode == 0:
            check_using_exec(host, exe, APPEND_NONE,
                             language, base_description, description, gnu_type,
                             environment_list)

def configure_language(host_list, environment_list, language, description, 
                       binary):
    """
    Configures the requested language by checking each host in host_list and
    adding detected environments to environment_list.
    """
    for host in host_list:
        if 'nt' in host.os_name_list:
            check_using_glob(host, r'c:\MinGW\bin\%s.exe' % binary, APPEND_NONE,
                             language, description, '%s (MinGW)' % description, 
                             GNU_WINDOWS, environment_list) 
            check_using_glob(host, r'c:\cygwin\bin\%s' % binary, APPEND_MAJOR, 
                             language, description, '%s (Cygwin)' % description,
                             GNU_CYGWIN, environment_list)
        else:
            check_using_which(host, binary, APPEND_NONE,
                              language, description, description, 
                              GNU_STANDARD, environment_list)
            check_using_which(host, binary, APPEND_MINOR,
                              language, description, description,
                              GNU_STANDARD, environment_list)

def configure(host_list, environment_list):
    configure_language(host_list, environment_list, _lang.C, 'gcc', 'gcc') 
    configure_language(host_list, environment_list, _lang.Cpp, 'g++', 'g++') 

class GnuEnvironment(_base.Environment):
    """
    An environment for one of the languages supported by the GNU Compiler 
    Collection.
    """

    has_arguments = True
    
    def __init__(self, language, host, path, version, gnu_type):
        self.language = language
        self.host = host
        self.path = path
        self.version = version
        self.gnu_type = gnu_type
        if gnu_type == GNU_WINDOWS or gnu_type == GNU_CYGWIN:
            self.exe_suffix = '.exe'
        else:
            self.exe_suffix = ''

    def _set_up_cygwin(self):
        """
        Sets up the environment for spawning a Cygwin process.
        """
        # TODO: Ought to just pass env= to popen...
        cygwin_env = self.host.getenv('CYGWIN')
        if cygwin_env is None:
            self.host.putenv('CYGWIN', 'nodosfilewarning')
        else:
            cygwin_env = re.sub(r'\b(no)?dosfilewarning', '', cygwin_env)
            self.host.putenv('CYGWIN', ' '.join([cygwin_env, 'nodosfilewarning']))

        self.host.environ_prepend_path(r'c:\cygwin\bin')
        
    def _tear_down_cygwin(self):
        """
        Cleans up (mostly) the environment after spawning a Cygwin process.
        """
        self.host.environ_reset_path()

        # Don't worry about restoring nodosfilewarning.  Most of our code
        # depends on it, and adding methods to support restoring it would
        # complicate the host interface.
        
    def compile(self, filename, output):
        """
        Compiles the given source file and sends the result to output.
        """
        if self.gnu_type == GNU_CYGWIN:
            self._set_up_cygwin()

        file_dirname = self.host.path_dirname(filename)
        file_basename = self.host.path_basename(filename)
        with LocalCwd(file_dirname, self.host):        
            gcc = self.host.popen([self.path, file_basename, 
                                   '-o', self.exe_filename_for(file_basename)], 
                                   stderr=subprocess.STDOUT)
            stdoutdata = gcc.communicate()[0]
            output.write(stdoutdata)

        if self.gnu_type == GNU_CYGWIN:
            self._tear_down_cygwin()

        return gcc.returncode == 0

    def execute(self, filename):
        if self.gnu_type == GNU_CYGWIN:
            self._set_up_cygwin()
            
        super(GnuEnvironment, self).execute(filename)
        
        if self.gnu_type == GNU_CYGWIN:
            self._tear_down_cygwin()
